Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new operator \J (skip search) #278 #299

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

add new operator \J (skip search) #278 #299

wants to merge 1 commit into from

Conversation

kkos
Copy link
Owner

@kkos kkos commented Jun 7, 2024

For "aaa..." (9000 chars)

/a+b/
real 0m0.174s
user 0m0.168s
sys 0m0.004s

/a+\Jb/
real 0m0.007s
user 0m0.002s
sys 0m0.004s

@RedCMD
Copy link

RedCMD commented Jun 7, 2024

thank you
this looks very promising

will \J cancel the search instantly or only after getting to the end of the regex?
eg. will a+\Jb match aaaaaaab?
or will it need to be something like a+(b|\J)?

@kkos
Copy link
Owner Author

kkos commented Jun 8, 2024

/a+\Jb/ match with "aaaaaaab".
\J has no effect on the current matching process and always succeeds.
When the current matching fails, it has an effect on the starting position of the next matching.
The next match is started at the most advanced position in the string position matched with \J. (However, if other optimizations result in a more advanced position, this has no effect.)

@tonco-miyazawa
Copy link

tonco-miyazawa commented Jun 10, 2024

@kkos (Owner), Thank you for adding this great feature. I would like to use the 'retraction only' version of (*MISMATCH) .

* MISMATCH (progress)
(*MISMATCH)
Terminates Match process.
Continues Search process.

Let's assume it is (*MISMATCH{<}) . Then, the following will have the same effect as (*SKIP) in perl5:
(*MISMATCH{<})\J or \J(*MISMATCH{<}) [EDIT] Not sure if this is correct.

In the following test, Backtracking of 'a+' is not performed. And skip to pos 'b'.
test(enc, mp, "a+(*MISMATCH{<})\\J$", "aaaaaaab");

Alternatively, we could choose to make \J incorporate the functionality of (*MISMATCH{<}) .
That is, this would make \J the same as perl5's (*SKIP).

Below is the behavior of (*SKIP) in perl5 that I have looked into in the past. (Japanese only)
[EDIT] This test reproduces a bug in Perl. It is not a useful reference.
https://github.com/tonco-miyazawa/regex_etc/blob/master/MEMO_perl5/Backtrack_ctrl/SKIP.txt


(Click to view) ( In Japanese)

素晴らしい機能をありがとうございます。私は (*MISMATCH) の "retractionのみ" のバージョンが使いたいです。
それを (*MISMATCH{<}) と仮定します。すると、以下はperl5の (*SKIP) と同じ動作になります。
(*MISMATCH{<})\J または \J(*MISMATCH{<}) [編集] これが正しいかどうか不明です。

以下のテストの場合、 "a+" のバックトラックは実行されることなく "b" のposまでスキップします。
test(enc, mp, "a+(*MISMATCH{<})\\J$", "aaaaaaab");

または \J(*MISMATCH{<}) の機能を組み込むという選択肢もあります。
つまりこれは \J を perl5の (*SKIP) と同じものにするということになります。

以下は私が過去に調べたperl5の (*SKIP) の動作です。 (上記リンク参照)
[編集] このテストはPerlのバグを再現してしまっています。参考になりません。

@kkos
Copy link
Owner Author

kkos commented Jun 10, 2024

I read it, but did not clearly understand the SKIP specifications.
Does the fact that it has an effect on another alternative mean that SKIP could affect the position of other parts of a single matching process?
My implementation is simple, it does not affect matching, only the next search position.
There is no need to use (*MISMATCH{<}) because \J only updates when the position advances.

I noticed that there is no need to use the operator \J.
I had forgotten about the callout.
I am thinking of canceling this PR and using (*SKIP).

@tonco-miyazawa
Copy link

tonco-miyazawa commented Jun 10, 2024

Sorry, I didn't explain it well. When backtracking reaches (*SKIP) in Perl5, it immediately ends Match-process
at the current position. [EDIT] Not sure if this is correct.

In the following test,
test(enc, mp, "ab(*SKIP)c|ab", "abd");

Backtracking reaches (*SKIP) because "c" in "abc" is not found.
In this case, (*SKIP) immediately ends Match-process at the current position,
so "ab" in the latter half of the regular expression is not tried at the same position.

It then skips to the next matching start position (after "ab", before "d"). As a result, this test returns "search fail" .
This behavior of immediately ending Match-process at the current position is the same as (*MISMATCH).

While (*MISMATCH) only works when moving forward, (*SKIP) in Perl5 only works when moving backward.
Below is a sample code in Perl5. You can turn (*SKIP) on or off with the comment "#".

#!/usr/bin/perl

use Encode;
use utf8;

my $str = 'abd';

if ($str =~ /ab(*SKIP)c|ab/) {       # (*SKIP)  exists
# if ($str =~ /abc|ab/) {            # no (*SKIP) exists

  print "Match! '$&'\n";
} else {
  print "Not match!\n";
}

(Click to view) ( In Japanese )

すみません、説明不足でした。Perl5 の (*SKIP) (*SKIP) 自身にバックトラックが到達した場合、
即座に現在位置での照合を終了させます。[編集] これが正しいかどうか不明です。

以下のテストの場合、
test(enc, mp, "ab(*SKIP)c|ab", "abd");

"abc" の "c" が見つからないのでバックトラックが (*SKIP) に到達します。
このとき、 (*SKIP) は現在位置での照合を即座に終了させるので
同じ位置で正規表現後半の "ab" が試されることはありません。

そして次の照合開始位置 ( "ab" の後ろ、 "d" の前) にスキップします。
その結果、このテストは "search fail" を返します。

この '現在位置での照合を即座に終了させる' という動作は (*MISMATCH) と同じです。
(*MISMATCH) は前進のときのみ動作しますが、 Perl5 の (*SKIP) は後退のときのみ動作します。

以下は Perl5 のサンプルコードです。(*SKIP) のあり、なし をコメント "#" で切り替えられます。(上記参照)

@kkos
Copy link
Owner Author

kkos commented Jun 12, 2024

I don't feel that the ability to exit when backing out is essential to (*SKIP).
(*MISMATCH) can only be used when moving forward, so something for retreating might be added later.

@tonco-miyazawa
Copy link

tonco-miyazawa commented Jun 13, 2024

@kkos (Owner)

I don't feel that the ability to exit when backing out is essential to (*SKIP).

Thank you for your consideration. I have no objection to your decision.
I think that the behavior of "\J" is simpler, easier to understand, and more flexible to use.

(*MISMATCH) can only be used when moving forward, so something for retreating might be added later.

It seems that this can be achieved by simply rewriting (*MISMATCH{<}) internally to (?:|(*MISMATCH)) .
(*MISMATCH) and (*MISMATCH{<}) can be converted to each other as follows:

(*MISMATCH{<}) == (?:|(*MISMATCH))
(*MISMATCH) == (*MISMATCH{<})(*FAIL)


Similarly for (*ERROR) and (*ERROR{<}) .
However, since (*ERROR) has an optional argument, you need to consider how to write {<} .

* ERROR (progress)
(*ERROR{n::LONG})
Terminates Search/Match process.
Return value is the argument 'n'. (The value must be less than -1)
'n' is an optional argument. (default value is ONIG_ABORT)

The idea below from @RedCMD is an alternative to (*ERROR{<}) .
#278 (comment)
( The above comment was very helpful. Thanks @RedCMD )

As an aside, (*MISMATCH{<}) and Perl5's (*PRUNE) are the same thing. [EDIT] Not sure if this is correct.
In other words, Effectively, Oniguruma already has Perl5's (*PRUNE) .


Click to view ( In Japanese )

I don't feel that the ability to exit when backing out is essential to (*SKIP).

ご検討ありがとうございました。 Kosako氏の判断に異論はありません。
現時点での "\J" のほうが動作がシンプルで分かりやすく、使い方に柔軟性があり優れていると思います。

(*MISMATCH) can only be used when moving forward, so something for retreating might be added later.

これは (*MISMATCH{<}) を内部的に (?:|(*MISMATCH)) に書き換えるだけで良さそうです。
(*MISMATCH)(*MISMATCH{<}) は以下のように相互変換が可能です。

(*MISMATCH{<}) == (?:|(*MISMATCH))
(*MISMATCH) == (*MISMATCH{<})(*FAIL)


(*ERROR)(*ERROR{<}) も同様です。
しかし、(*ERROR) はオプション引数を持つので {<} の書き方を考える必要があります。

* ERROR (前進)
(*ERROR{n::LONG})
検索/照合を中止する
戻り値は引数'n'の値。(-1より小さい負の値でなければならない)
'n'はオプション引数で、デフォルト値はONIG_ABORT

以下の @RedCMD さんのアイデアは (*ERROR{<}) の代替案そのものです。
#278 (comment)
( 上記のコメントはとても参考になりました。 @RedCMD さん、ありがとうございます )

余談ですが、 (*MISMATCH{<}) は Perl5 の (*PRUNE) と同じ動作になります。[編集] これが正しいかどうか不明です。
つまり事実上、 oniguruma はPerl5 の (*PRUNE) を実装済みです。

kkos added a commit that referenced this pull request Jun 15, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
@tonco-miyazawa
Copy link

tonco-miyazawa commented Dec 5, 2024

Additional Information: (*Error{<}) and Perl5 (*COMMIT) have the same behavior. [EDIT] Not sure if this is correct.

Perl5 doc
https://metacpan.org/dist/perl/view/pod/perlre.pod#(*SKIP)-(*SKIP:NAME)
https://metacpan.org/dist/perl/view/pod/perlre.pod#(*PRUNE)-(*PRUNE:NAME)
https://metacpan.org/dist/perl/view/pod/perlre.pod#(*COMMIT)-(*COMMIT:arg)

Perl5 doc (Japanese version) 特殊なバックトラック制御記号
https://perldoc.jp/docs/perl/5.40.0/perlre.pod#Special32Backtracking32Control32Verbs

@tonco-miyazawa

This comment was marked as off-topic.

@tonco-miyazawa
Copy link

tonco-miyazawa commented Feb 6, 2025

[EDIT] WARNING: The following Perl result may be a bug.
What follows may be meaningless.

Below are tests to see if (*VERB) will terminate the 'Match-process'.
(*SKIP), (*PRUNE) and (*COMMIT) do not terminate the 'Match-process'. Therefore, \d can match '0'.

#!/usr/bin/perl
if ('0123' =~ /
  (?!
    (*SKIP)(?!)
  )
  (?!
    (*PRUNE)(?!)
  )
  (?!
    (*COMMIT)(?!)
  ) 
  \d
/x ) { print "Match! '$&'\n"; } else { print "No match!\n"; }
# Result: Match! '0'

(*MISMATCH) and (*ERROR) terminate the 'Match-process'. Therefore, \d cannot match anything.
----- test_utf8.c ------

  n("(?!(*MISMATCH)(?!))\\d", "0123");
  e("(?!(*ERROR)(?!))\\d", "0123", ONIG_ABORT);

/* Result:
OK(N): /(?!(*MISMATCH)(?!))\d/ '0123'  #1789              // Search Failed
OK(ERROR): /(?!(*ERROR)(?!))\d/ '0123', -3  #1790         // ONIG_ABORT error occurred  */ 

I didn't notice this difference for a long time, sorry. I have done similar test in the past but misinterpreted the results.

In Japanese

(*SKIP), (*PRUNE), (*COMMIT) の3つは "マッチプロセスを終了する" という動作を持っていません。
従って、\d は '0' にマッチ可能です。

(*MISMATCH)(*ERROR) は "マッチプロセスを終了する" という動作を持っています。
従って、\d はマッチ不可能です。

私は今までこの両者の違いに長い間気付きませんでした、申し訳ありませんでした。
私は過去に同様のテストを行い、この違いを示すテスト結果が出ていたにも関わらず解釈を誤りました。

@kkos
Copy link
Owner Author

kkos commented Feb 7, 2025

@tonco-miyazawa
It may be different from what you imagine,
*MISMATCH and *ERROR immediately terminate the matching process.
(doc/CALLOUTS.BUILTIN)

@tonco-miyazawa
Copy link

tonco-miyazawa commented Feb 7, 2025

( Japanese version of next comment )

申し訳ないです、その点は理解出来ていたのですが翻訳英語では伝わりませんでした。

(*MISMATCH)(*ERROR) はこれらが先読みの中で使われた場合、先読みの内側の
マッチプロセスだけでなく先読みの外側のマッチプロセスも終了させます。
しかし、(*COMMIT) はそうではありません。

(*COMMIT) は先読みの内側のマッチプロセスを終了させますが、先読みの外側の
マッチプロセスは終了させません。
先読みの外側のマッチプロセスは走り続け、最後までマッチした場合はマッチ成功になります。

否定先読みの中の (*COMMIT) は否定先読みの内側をマッチ失敗にさせますが、
否定先読みなので内側がマッチに失敗すると、否定先読み自身はマッチに成功します。

そのため、否定先読みの外側のマッチプロセスはマッチングを続行します。
上記のPerlのテストで \d が '0' にマッチするのはこのためです。


先読みの外側のマッチプロセスもマッチに失敗した場合、(*COMMIT)
pos を進めることを許さず、サーチプロセス全体を終了させます。
その結果、マッチ失敗になります。

この動作は上記のPerlのテストで \d[^0] に書き換えることで再現出来ます。

[^0] は '0' にマッチすることが出来ないため、pos 0 では先読みの外側のマッチプロセスは
マッチに失敗します。
この場合、(*COMMIT) はposを進めることを許さず、結果はマッチ失敗になります。
そのため、pos 1 に進んで [^0] が '1' にマッチする、という結果にはなりません。


"先読みの内側のマッチプロセスを終了させ、先読みの外側のマッチプロセスは終了しない"
という動作はPerlの (*PRUNE)(*SKIP) にも共通の動作です。

現時点での私の認識では (*PRUNE) が基本形であり、先読みの外側のマッチが失敗した場合に
pos をどう進めるかの動作が追加されたのが (*COMMIT)(*SKIP) であると考えています。


以上のことから (*MISMATCH{<})(*ERROR{<})(*PRUNE), (*COMMIT) とは
同じものになり得ないことがお分かり頂けたと思います。

(*MISMATCH)(*ERROR) はプロセスを終了させるためのものですが、(*PRUNE)
(?>...) のようにバックトラックを制御するためのものなので根本的に別物です。

今回は Kosako氏に誤った実装をさせてしまってもおかしくありませんでした。
大変申し訳ありませんでした。

@tonco-miyazawa
Copy link

@kkos (Owner)
I asked you to create the wrong operator. I apologize for this.

For berow reasons, the behavior of (*MISMATCH{<}) is not the same as the behavior of (*PRUNE).
Similarly, the behavior of (*ERROR{<}) is not the same as the behavior of (*COMMIT).

(*MISMATCH) and (*ERROR) terminate the Match-process both inside and outside the look-ahead.

(*PRUNE), (*SKIP) and (*COMMIT) terminate the Match-process inside the look-ahead.
They do not terminate the Match-process outside of the look-ahead.

If the Match-process outside of the look-ahead matches to the end, the result is successful.

If the Match-process outside of the look-ahead fails, the position moves forward.
At that point, (*COMMIT) will end the Search-process.
Similarly, (*SKIP) will skip a position.

In the above Perl test, if you change \d to [^0], the result will 'No match!'.
The reason is that (*COMMIT) prevents the position from moving.


If the match process inside 'negative look-ahead' fails, then 'negative look-ahead' succeeds.
(*COMMIT) causes the Match-process inside 'negative look-ahead' to fail.
Therefore,'negative look-ahead' succeeds.

[Added] Backtracking is required for (*COMMIT) to work. (?!) causes backtracking.


The following are behaviors common to (*PRUNE), (*SKIP), and (*COMMIT).

  • Terminates the Match-process inside 'look-ahead'.
  • Does not terminate the Match-process outside 'look-ahead'.

At the moment I think the following:

  • (*PRUNE) is the basis.
  • Position control behavior has been added to (*SKIP) and (*COMMIT).
  • This behavior only works if the Match-process outside 'look-ahead' fails.

(*MISMATCH) and (*ERROR) are used to terminate a process, but (*PRUNE) is used to
control backtracking as like (?>...), so they are fundamentally different.


*MISMATCH and *ERROR immediately terminate the matching process.

I knew about this. Maybe my translation was wrong, sorry.
[Added] I have revised my previous comment. I have also added Japanese.

@tonco-miyazawa
Copy link

I found that adding (?=) to the end of the regular expression in the Perl test above caused it to not match.
上記のPerlテストの正規表現の最後に (?=) を追加するとマッチしなくなることが分かりました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants